package com.sunday.minio.core;

import com.sunday.minio.autoconfiguration.MinioProperties;
import io.minio.*;
import io.minio.messages.DeleteObject;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.function.Supplier;

/**
 * MinIO operations
 *
 * @author zsy
 * @since 2024/1/25
 */
public interface MinioOperations {

    default ObjectWriteResponse uploadObject(String name, String path) {
        return uploadObject(defaultBucket(), name, path);
    }

    /**
     * 通过桶名称、对象名称和文件路径上传文件。
     *
     * @param bucket 桶名称
     * @param name   文件名称(需要唯一,否则会覆盖)   uploadObject
     * @param path   上传文件完整路径              D:\app\minIO\447680d47859c66d417a7bbaaa70b21c.jpeg
     * @return {@link ObjectWriteResponse}
     * @see UploadObjectArgs#contentType()
     */
    default ObjectWriteResponse uploadObject(String bucket, String name, String path) {
        return invoke(() -> getMinioClient().uploadObject(
                UploadObjectArgs.builder()
                        .bucket(bucket)
                        .object(name)
                        .filename(path)
                        .build()));
    }

    default ObjectWriteResponse putObject(String name, String contentType, byte[] bytes) {
        return putObject(defaultBucket(), name, contentType, bytes);
    }

    /**
     * 通过桶名称、对象名称、文件类型和内容字节上传文件。
     *
     * @param bucket      桶名称
     * @param name        文件名称(需要唯一,否则会覆盖)     putObject
     * @param contentType 文件类型                       text/plain
     * @param bytes       文件内容字节
     * @return {@link ObjectWriteResponse}
     * @see PutObjectArgs#contentType()
     */
    default ObjectWriteResponse putObject(String bucket, String name, String contentType, byte[] bytes) {
        return invoke(() -> {
            try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
                return getMinioClient().putObject(
                        PutObjectArgs.builder()
                                .bucket(bucket)
                                .object(name)
                                .contentType(contentType)
                                .stream(bais, bais.available(), -1)
                                .build());
            }
        });
    }

    default ObjectWriteResponse putObject(String name, String contentType, ResourceProvider<InputStream> callback) {
        return putObject(defaultBucket(), name, contentType, callback);
    }

    /**
     * 将给定的流作为存储桶中的对象上传。
     *
     * @param bucket      桶名称
     * @param name        文件名称(需要唯一,否则会覆盖)   abc.mp3 = Paths.get(multipartFile.getOriginalFilename()) -> MinioUtils.objectName(path)
     * @param contentType 文件类型                    audio/mpeg = Paths.get(multipartFile.getOriginalFilename()) -> MinioUtils.contentType(path)
     * @param provider    资源提供者
     * @return {@link ObjectWriteResponse}
     */
    default ObjectWriteResponse putObject(String bucket, String name, String contentType, ResourceProvider<InputStream> provider) {
        return invoke(() -> {
            try (InputStream input = provider.get()) {
                return getMinioClient().putObject(
                        PutObjectArgs.builder()
                                .bucket(bucket)
                                .object(name)
                                .contentType(contentType)
                                .stream(input, input.available(), -1)
                                .build());
            }
        });
    }

    /**
     * 在单个放置调用中上传多个对象。这是通过创建选择性压缩到S3服务的中间TAR文件来完成的。
     *
     * @param bucket  桶名称
     * @param objects 批量文件上传对象集合
     * @return {@link ObjectWriteResponse}
     */
    default ObjectWriteResponse uploadSnowballObjects(String bucket, Supplier<Collection<SnowballObject>> objects) {
        return invoke(() -> getMinioClient().uploadSnowballObjects(
                UploadSnowballObjectsArgs.builder()
                        .bucket(bucket)
                        .objects(objects.get())
                        .build()));
    }

    default ObjectWriteResponse copyObject(String copyName, String goalName) {
        return copyObject(defaultBucket(), copyName, defaultBucket(), goalName);
    }

    /**
     * 复制文件
     *
     * @param copyBucket 复制桶名称
     * @param copyName   复制文件名称(需要唯一,否则会覆盖)  2024/01/26/448b136b537a38fb55565880436a15f9.txt
     * @param goalBucket 目标桶名称
     * @param goalName   目标文件名称(需要唯一,否则会覆盖)  2024/01/26/123.txt
     * @return {@link ObjectWriteResponse}
     */
    default ObjectWriteResponse copyObject(String copyBucket, String copyName, String goalBucket, String goalName) {
        return invoke(() -> getMinioClient().copyObject(
                CopyObjectArgs.builder()
                        .bucket(goalBucket)
                        .object(goalName)
                        .source(
                                CopySource.builder()
                                        .bucket(copyBucket)
                                        .object(copyName)
                                        .build())
                        .build()
        ));
    }

    default void removeObject(String name) {
        removeObject(defaultBucket(), name);
    }

    /**
     * 删除对象。
     *
     * @param bucket 桶名称
     * @param name   minio中的完整路径 2023/10/17/30c4d7673f77b257c8e26859ebf04213.txt
     */
    default void removeObject(String bucket, String name) {
        invoke(() -> {
            getMinioClient().removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(bucket)
                            .object(name)
                            .build());
            return true;
        });
    }

    default void removeObjects(Collection<DeleteObject> objects) {
        removeObjects(defaultBucket(), objects);
    }

    /**
     * 延迟删除多个对象。
     * 需要迭代返回的 Iterable 才能执行删除。
     *
     * @param bucket
     * @param objects
     */
    default void removeObjects(String bucket, Collection<DeleteObject> objects) {
        invoke(() -> getMinioClient()
                .removeObjects(
                        RemoveObjectsArgs.builder()
                                .bucket(bucket)
                                .objects(objects)
                                .build()
                )
                .iterator()
                .hasNext()
        );
    }

    default InputStream getObject(String name) {
        return getObject(defaultBucket(), name);
    }

    /**
     * 获取对象的数据。
     *
     * @param bucket 桶名称
     * @param name   文件名称(需要唯一,否则会覆盖) 2024/01/26/ef11c5e18e8cc32c0d25be13111954fd.txt
     * @return {@link InputStream} 返回必须在使用后关闭以释放网络资源 GetObjectResponse extends FilterInputStream
     * @see GetObjectResponse
     */
    default InputStream getObject(String bucket, String name) {
        return invoke(() -> getMinioClient().getObject(
                GetObjectArgs.builder()
                        .bucket(bucket)
                        .object(name)
                        .build()
        ));
    }

    default void downloadObject(String name, String path) {
        downloadObject(defaultBucket(), name, path);
    }

    /**
     * 将对象的数据下载到文件。
     *
     * @param bucket 桶名称
     * @param name   文件名称(需要唯一,否则会覆盖) 2024/01/26/ef11c5e18e8cc32c0d25be13111954fd.txt
     * @param path   下载文件完整路径
     */
    default void downloadObject(String bucket, String name, String path) {
        invoke(() -> {
            getMinioClient().downloadObject(
                    DownloadObjectArgs.builder()
                            .bucket(bucket)
                            .object(name)
                            .filename(path)
                            .build());
            return true;
        });
    }


    @FunctionalInterface
    interface ResourceProvider<T> {
        T get() throws Exception;
    }

    default <T> T invoke(ResourceProvider<T> provider) {
        try {
            return provider.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    MinioClient getMinioClient();

    MinioProperties getMinioProperties();

    default String defaultBucket() {
        return getMinioProperties().getBucket();
    }

}
